﻿using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.UI;

public class SearchInputField : InputField
{
    [SerializeField]
    private RectTransform m_Template;
    public RectTransform template { get { return m_Template; } set { m_Template = value; } }

    [SerializeField]
    private List<string> m_Options = new List<string>();
    public List<string> options { get { return m_Options; } set { m_Options = value; } }

    [Serializable]
    public class SearchInputFieldEvent : UnityEvent<string> { }
    [SerializeField]
    private SearchInputFieldEvent m_OnMatched = new SearchInputFieldEvent();
    public SearchInputFieldEvent onMatched { get { return m_OnMatched; } set { m_OnMatched = value; } }

    private List<string> _matchResults = new List<string>();
    private List<DropdownItem> _items = new List<DropdownItem>();

    private bool _isValidTemplate = false;
    private GameObject _dropdown;
    private GameObject _blocker;

    protected override void Awake()
    {
        base.Awake();
    }

    protected override void OnEnable()
    {
        base.OnEnable();

    }

    protected override void Start()
    {
        base.Start();

        onValueChanged.AddListener(txt => { MatchTextAndShow(txt); });
    }

    protected override void OnDisable()
    {
        base.OnDisable();

    }

    protected override void OnDestroy()
    {
        base.OnDestroy();

    }

    private void MatchTextAndShow(string txt)
    {
        if (string.IsNullOrEmpty(txt))
        {
            Hide();
        }
        else
        {
            _matchResults.Clear();
            //foreach (string item in m_Options)
            //{
            //    if (item.Contains(txt))
            //    {
            //        _matchResults.Add(item);
            //    }
            //}
            _matchResults = PinYinConverter.Match(m_Options, txt);

            if (_matchResults.Count > 0)
            {
                Hide();
                Show();
            }
            else
            {
                Hide();
            }
        }
    }

    public void SetTextDontActiveEvent(string text)
    {
        m_Text = text;
    }

    protected internal class DropdownItem : MonoBehaviour
    {
        [SerializeField]
        private Text m_Text;
        [SerializeField]
        private RectTransform m_RectTransform;
        [SerializeField]
        private Button m_Button;

        public Text text { get { return m_Text; } set { m_Text = value; } }
        public RectTransform rectTransform { get { return m_RectTransform; } set { m_RectTransform = value; } }
        public Button button { get { return m_Button; } set { m_Button = value; } }
    }

    public void AddOption(string option)
    {
        m_Options.Add(option);
    }

    public void AddOptions(List<string> options)
    {
        m_Options.AddRange(options);
    }

    public void ClearOptions()
    {
        m_Options.Clear();
    }

    private void SetupTemplate()
    {
        GameObject templateGo = template.gameObject;
        templateGo.SetActive(true);
        Button itemBtn = template.GetComponentInChildren<Button>();
        Text itemTxt = template.GetComponentInChildren<Text>();

        DropdownItem item = itemBtn.gameObject.AddComponent<DropdownItem>();
        item.text = itemTxt;
        item.button = itemBtn;
        item.rectTransform = (RectTransform)itemBtn.transform;

        Canvas popupCanvas = GetOrAddComponent<Canvas>(templateGo);
        popupCanvas.overrideSorting = true;
        popupCanvas.sortingOrder = 30000;

        GetOrAddComponent<GraphicRaycaster>(templateGo);
        GetOrAddComponent<CanvasGroup>(templateGo);
        templateGo.SetActive(false);
        
        _isValidTemplate = true;
    }

    private static T GetOrAddComponent<T>(GameObject go) where T : Component
    {
        T comp = go.GetComponent<T>();
        if (!comp)
            comp = go.AddComponent<T>();
        return comp;
    }

    private void Show()
    {
        if (!IsActive() || !IsInteractable()) return;

        if (!_isValidTemplate)
        {
            SetupTemplate();
            if (!_isValidTemplate)
                return;
        }

        List<Canvas> list = new List<Canvas>();
        gameObject.GetComponentsInParent(false, list);
        if (list.Count == 0)
            return;
        Canvas rootCanvas = list[0];

        template.gameObject.SetActive(true);

        // Instantiate the drop-down template
        _dropdown = Instantiate(template.gameObject);
        _dropdown.name = "Dropdown List";
        _dropdown.SetActive(true);
        RectTransform dropdownRectTransform = _dropdown.transform as RectTransform;
        dropdownRectTransform.SetParent(template.transform.parent, false);

        // Find the dropdown item and disable it.
        DropdownItem itemTemplate = _dropdown.GetComponentInChildren<DropdownItem>();

        GameObject content = itemTemplate.rectTransform.parent.gameObject;
        RectTransform contentRectTransform = content.transform as RectTransform;
        itemTemplate.rectTransform.gameObject.SetActive(true);

        // Get the rects of the dropdown and item
        Rect dropdownContentRect = contentRectTransform.rect;
        Rect itemTemplateRect = itemTemplate.rectTransform.rect;

        // Calculate the visual offset between the item's edges and the background's edges
        Vector2 offsetMin = itemTemplateRect.min - dropdownContentRect.min + (Vector2)itemTemplate.rectTransform.localPosition;
        Vector2 offsetMax = itemTemplateRect.max - dropdownContentRect.max + (Vector2)itemTemplate.rectTransform.localPosition;
        Vector2 itemSize = itemTemplateRect.size;

        _items.Clear();
        Button prev = null;
        for (int i = 0; i < _matchResults.Count; ++i)
        {
            string data = _matchResults[i];
            DropdownItem item = AddItem(data, itemTemplate, _items);
            if (item == null)
                continue;

            // Automatically set up a toggle state change listener
            item.button.onClick.AddListener(() =>
            {
                m_Text = item.text.text;
                onMatched.Invoke(m_Text);
                //MoveTextEnd(true);
                Hide();
            });

            // 不要切换焦点
            //item.button.Select();

            // Automatically set up explicit navigation
            if (prev != null)
            {
                Navigation prevNav = prev.navigation;
                Navigation toggleNav = item.button.navigation;
                prevNav.mode = Navigation.Mode.Explicit;
                toggleNav.mode = Navigation.Mode.Explicit;

                prevNav.selectOnDown = item.button;
                prevNav.selectOnRight = item.button;
                toggleNav.selectOnLeft = prev;
                toggleNav.selectOnUp = prev;

                prev.navigation = prevNav;
                item.button.navigation = toggleNav;
            }
            prev = item.button;
        }

        // Reposition all items now that all of them have been added
        Vector2 sizeDelta = contentRectTransform.sizeDelta;
        sizeDelta.y = itemSize.y * _items.Count + offsetMin.y - offsetMax.y;
        contentRectTransform.sizeDelta = sizeDelta;

        float extraSpace = dropdownRectTransform.rect.height - contentRectTransform.rect.height;
        if (extraSpace > 0)
            dropdownRectTransform.sizeDelta = new Vector2(dropdownRectTransform.sizeDelta.x, dropdownRectTransform.sizeDelta.y - extraSpace);

        // Invert anchoring and position if dropdown is partially or fully outside of canvas rect.
        // Typically this will have the effect of placing the dropdown above the button instead of below,
        // but it works as inversion regardless of initial setup.
        Vector3[] corners = new Vector3[4];
        dropdownRectTransform.GetWorldCorners(corners);

        RectTransform rootCanvasRectTransform = rootCanvas.transform as RectTransform;
        Rect rootCanvasRect = rootCanvasRectTransform.rect;
        for (int axis = 0; axis < 2; axis++)
        {
            bool outside = false;
            for (int i = 0; i < 4; i++)
            {
                Vector3 corner = rootCanvasRectTransform.InverseTransformPoint(corners[i]);
                if (corner[axis] < rootCanvasRect.min[axis] || corner[axis] > rootCanvasRect.max[axis])
                {
                    outside = true;
                    break;
                }
            }
            if (outside)
                RectTransformUtility.FlipLayoutOnAxis(dropdownRectTransform, axis, false, false);
        }

        for (int i = 0; i < _items.Count; i++)
        {
            RectTransform itemRect = _items[i].rectTransform;
            itemRect.anchorMin = new Vector2(itemRect.anchorMin.x, 0);
            itemRect.anchorMax = new Vector2(itemRect.anchorMax.x, 0);
            itemRect.anchoredPosition = new Vector2(itemRect.anchoredPosition.x, offsetMin.y + itemSize.y * (_items.Count - 1 - i) + itemSize.y * itemRect.pivot.y);
            itemRect.sizeDelta = new Vector2(itemRect.sizeDelta.x, itemSize.y);
        }

        // Make drop-down template and item template inactive
        template.gameObject.SetActive(false);
        itemTemplate.gameObject.SetActive(false);

        _blocker = CreateBlocker(rootCanvas);

        // 焦点
        //EventSystem.current.SetSelectedGameObject(gameObject);
        //MoveTextEnd(false);
    }

    public void Hide()
    {
        if (_dropdown != null)
        {
            for (int i = 0; i < _items.Count; i++)
            {
                if (_items[i] != null)
                {
                    //DestroyItem(_items[i]);
                }
            }
            _items.Clear();
            if (_dropdown != null)
                Destroy(_dropdown);
            _dropdown = null;
        }

        if (_blocker != null)
            Destroy(_blocker);
        _blocker = null;
        Select();
    }

    private GameObject CreateBlocker(Canvas rootCanvas)
    {
        // Create blocker GameObject.
        GameObject blocker = new GameObject("Blocker");

        // Setup blocker RectTransform to cover entire root canvas area.
        RectTransform blockerRect = blocker.AddComponent<RectTransform>();
        blockerRect.SetParent(rootCanvas.transform, false);
        blockerRect.anchorMin = Vector3.zero;
        blockerRect.anchorMax = Vector3.one;
        blockerRect.sizeDelta = Vector2.zero;

        // Make blocker be in separate canvas in same layer as dropdown and in layer just below it.
        Canvas blockerCanvas = blocker.AddComponent<Canvas>();
        blockerCanvas.overrideSorting = true;
        Canvas dropdownCanvas = _dropdown.GetComponent<Canvas>();
        blockerCanvas.sortingLayerID = dropdownCanvas.sortingLayerID;
        blockerCanvas.sortingOrder = dropdownCanvas.sortingOrder - 1;

        // Add raycaster since it's needed to block.
        blocker.AddComponent<GraphicRaycaster>();

        // Add image since it's needed to block, but make it clear.
        Image blockerImage = blocker.AddComponent<Image>();
        blockerImage.color = Color.clear;

        // Add button since it's needed to block, and to close the dropdown when blocking area is clicked.
        Button blockerButton = blocker.AddComponent<Button>();
        blockerButton.onClick.AddListener(Hide);

        return blocker;
    }
    
    private DropdownItem AddItem(string data, DropdownItem itemTemplate, List<DropdownItem> items)
    {
        // Add a new item to the dropdown.
        DropdownItem item = Instantiate(itemTemplate);
        item.rectTransform.SetParent(itemTemplate.rectTransform.parent, false);

        item.gameObject.SetActive(true);
        item.gameObject.name = "Item " + items.Count + (!string.IsNullOrEmpty(data) ? ": " + data : "");

        // Set the item's data
        if (item.text)
            item.text.text = data;

        items.Add(item);
        return item;
    }
}
